﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Xml;
using System.Collections;
using System.Globalization;
using System.IO;

namespace HRM.Utilities
{
    /// <summary>
    /// Làm việc với file Word ML.
    /// </summary>
    public  class CExWordMLFiller
    {
        private DataSet dsData;
        private string templateContent;
        private XmlDocument xmlTemplateDoc;
        private XmlDocument xmlDataset;
        private XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
        private ArrayList errorList = new ArrayList();
        private bool bOperationFailed = false;
        private static string wordmlPrefix = "http://schemas.microsoft.com/office/word/2003/wordml";
        private static string amlPrefix = "http://schemas.microsoft.com/aml/2001/core";
        private static string xslPrefix = "http://www.w3.org/1999/XSL/Transform";
        private static string xsPrefix = "http://www.w3.org/2001/XMLSchema";
        private static string vPrefix = "urn:schemas-microsoft-com:vml";
        private const string repeatAttribute = "WMLRepeat";

        private string languageName;
        private CultureInfo ci;
        private string dateTimeFormat;
        private NumberFormatInfo numberFormat;
        private string numberDecimalSeparator;
        private string numberGroupSeparator;
        private int numberDecimalDigits;

        //CallBackk
        public delegate void CallbackFill(XmlNode dataNode, string field, object data);
        public static event CallbackFill CallBack;


        public CExWordMLFiller(DataSet dsData, string templateContent)
        {
            this.dsData = dsData;
            xmlDataset = new XmlDocument();
            MemoryStream stream = new MemoryStream();
            dsData.WriteXml(stream, XmlWriteMode.WriteSchema);
            stream.Seek(0, SeekOrigin.Begin);
            XmlReader reader = new XmlTextReader(stream);
            xmlDataset.Load(reader);
            this.templateContent = templateContent;
            LoadTemplate();
        }

        private void LoadTemplate()
        {
            errorList = new ArrayList();
            bOperationFailed = false;
            try
            {
                xmlTemplateDoc = new XmlDocument();
                xmlTemplateDoc.LoadXml(this.templateContent);
                nsmgr.AddNamespace("w", wordmlPrefix);
                nsmgr.AddNamespace("aml", amlPrefix);
                nsmgr.AddNamespace("v", vPrefix);
                XmlNode varNode = xmlTemplateDoc.SelectSingleNode("//w:docVar[@w:name='LanguageName']/@w:val", nsmgr);
                if (varNode != null)
                {
                    languageName = varNode.Value;
                    try
                    {
                        ci = new CultureInfo(languageName, false);
                        numberFormat = ci.NumberFormat;
                    }
                    catch
                    {
                        languageName = null;
                        numberFormat = null;
                    }
                }
                else
                {
                    languageName = null;
                }
                varNode = xmlTemplateDoc.SelectSingleNode("//w:docVar[@w:name='DateTimeFormat']/@w:val", nsmgr);
                if (varNode != null)
                {
                    dateTimeFormat = varNode.Value;
                }
                else
                {
                    dateTimeFormat = null;
                }
                varNode = xmlTemplateDoc.SelectSingleNode("//w:docVar[@w:name='NumberDecimalSeparator']/@w:val", nsmgr);
                if (varNode != null)
                {
                    numberDecimalSeparator = varNode.Value;
                    if (numberFormat == null) numberFormat = new NumberFormatInfo();
                    numberFormat.NumberDecimalSeparator = numberDecimalSeparator.Trim() == string.Empty ? "." : numberDecimalSeparator;
                }
                else
                {
                    numberDecimalSeparator = null;
                }
                varNode = xmlTemplateDoc.SelectSingleNode("//w:docVar[@w:name='NumberGroupSeparator']/@w:val", nsmgr);
                if (varNode != null)
                {
                    numberGroupSeparator = varNode.Value;
                    if (numberFormat == null) numberFormat = new NumberFormatInfo();
                    numberFormat.NumberGroupSeparator = numberGroupSeparator.Trim() == string.Empty ? "," : numberGroupSeparator;
                }
                else
                {
                    numberGroupSeparator = null;
                }
                varNode = xmlTemplateDoc.SelectSingleNode("//w:docVar[@w:name='NumberDecimalDigits']/@w:val", nsmgr);
                if (varNode != null)
                {
                    try
                    {
                        numberDecimalDigits = int.Parse(varNode.Value);
                    }
                    catch
                    {
                        numberDecimalDigits = -1;
                    }
                    if (numberFormat == null) numberFormat = new NumberFormatInfo();
                    numberFormat.NumberDecimalDigits = numberDecimalDigits == -1 ? 2 : numberDecimalDigits;
                }
                else
                {
                    numberDecimalDigits = -1;
                }
            }
            catch (Exception ex)
            {
                while (ex != null)
                {
                    errorList.Add(ex.Message);
                    ex = ex.InnerException;
                }
                bOperationFailed = true;
            }
        }

        #region Public properties

        public bool OperationFailed
        {
            get
            {
                return bOperationFailed;
            }
        }

        public XmlDocument WordMLDocument
        {
            get
            {
                return xmlTemplateDoc;
            }
        }

        public ArrayList ErrorList
        {
            get
            {
                return errorList;
            }
        }

        #endregion

        private void RemoveMailMergeNode()
        {
            errorList = new ArrayList();
            bOperationFailed = false;
            XmlNode mailMergeNode = xmlTemplateDoc.SelectSingleNode("//w:mailMerge", nsmgr);
            if (mailMergeNode == null)
            {
                errorList.Add("Invalid mail merge node!");
                bOperationFailed = true;
                return;
            }
            XmlNode parentNode = mailMergeNode.ParentNode;
            if (parentNode == null)
            {
                errorList.Add("Invalid mail merge parent node!");
                bOperationFailed = true;
                return;
            }
            parentNode.RemoveChild(mailMergeNode);
        }

        private void RemoveUnfilledFields()
        {
            errorList = new ArrayList();
            bOperationFailed = false;
            nsmgr.AddNamespace("xs", xsPrefix);
            XmlNodeList oColl = xmlDataset.SelectNodes("//xs:element", nsmgr);
            if (oColl == null || oColl.Count == 0)
                return;
            string fieldName;
            foreach (XmlNode node in oColl)
            {
                fieldName = ((XmlElement)node).GetAttribute("name");
                ReplaceFieldData(xmlTemplateDoc.DocumentElement, fieldName, string.Empty, Type.Missing.GetType());
            }
        }

        private void RemoveUnfilledImage()
        {
            errorList = new ArrayList();
            bOperationFailed = false;
            XmlNode mailMergeNode = xmlTemplateDoc.SelectSingleNode("//w:p/w:r/w:pict", nsmgr);
            if (mailMergeNode == null)
            {
                errorList.Add("Invalid mail merge node!");
                bOperationFailed = true;
                return;
            }
            XmlNode parentNode = mailMergeNode.ParentNode;
            if (parentNode == null)
            {
                errorList.Add("Invalid mail merge parent node!");
                bOperationFailed = true;
                return;
            }
            parentNode.RemoveChild(mailMergeNode);
        }

        private void ReplaceImageData(XmlNode baseNode, string imagePath)
        {
            errorList = new ArrayList();
            bOperationFailed = false;
            string attributeValue;
            //select the collection of paragraph elements containing image definition
            XmlNodeList oColl;

            oColl = baseNode.SelectNodes("//w:p/w:r/w:pict/v:shape", nsmgr);

            if (oColl == null || oColl.Count == 0)
            {
                errorList.Add("There is no image!");
                bOperationFailed = true;
                return;
            }

            foreach (XmlElement element in oColl)
            {
                attributeValue = element.GetAttribute("style");
                element.SetAttribute("style", "width:34pt;height:34pt");
            }

            oColl = baseNode.SelectNodes("//w:p/w:r/w:pict/v:shape/v:imagedata", nsmgr);

            if (oColl == null || oColl.Count == 0)
            {
                errorList.Add("There is no image!");
                bOperationFailed = true;
                return;
            }

            foreach (XmlElement element in oColl)
            {
                attributeValue = element.GetAttribute("src");
                element.SetAttribute("src", imagePath);
            }
        }

        private void ReplaceFieldData(XmlNode baseNode, string fieldName, string data, Type colType)
        {
            errorList = new ArrayList();
            bOperationFailed = false;
            XmlNode dataNode, oDelNode;
            //select the collection of paragraph elements containing merge field definition
            XmlNodeList oColl, oDelColl;
            oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText=' MERGEFIELD " + fieldName + " ']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText='MERGEFIELD " + fieldName + "']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText=' MERGEFIELD \"" + fieldName + "\" ']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText='MERGEFIELD \"" + fieldName + "\"']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText=' MERGEFIELD  " + fieldName + " ']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText='MERGEFIELD  " + fieldName + "']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText=' MERGEFIELD  \"" + fieldName + "\" ']", nsmgr);
            if (oColl == null || oColl.Count == 0)
                oColl = baseNode.SelectNodes("//w:p[w:r/w:instrText='MERGEFIELD  \"" + fieldName + "\"']", nsmgr);

            if (oColl == null || oColl.Count == 0)
            {
                //errorList.Add("There is no merge field with specified field name (" + fieldName + ")!");
                //bOperationFailed = true;
                return;
            }

            foreach (XmlNode fieldNode in oColl)
            {
                //select run element containing the underlying data
                dataNode = fieldNode.SelectSingleNode("//w:t[.='«" + fieldName + "»']", nsmgr);
                if (dataNode == null)
                {
                    errorList.Add("The field data is selected from the fields definition data source or merge document is corrupted!");
                    bOperationFailed = true;
                    return;
                }
                if (colType == typeof(DateTime))
                {
                    if (dateTimeFormat != null)
                    {
                        DateTime dt;
                        if (DateTime.TryParse(data, out dt))
                            dataNode.InnerText = dt.ToString(dateTimeFormat);
                        else
                            dataNode.InnerText = "";
                    }
                    else
                    {
                        DateTime dt;
                        if (DateTime.TryParse(data, out dt))
                            dataNode.InnerText = dt.ToString("dd/MM/yyyy");
                        else
                            dataNode.InnerText = "";
                    }
                }
                else if (colType == typeof(int))
                {
                    if (numberFormat != null)
                    {
                        int i;
                        if (int.TryParse(data, out i))
                            dataNode.InnerText = i.ToString(numberFormat);
                        else
                            dataNode.InnerText = "0";
                    }
                    else
                    {
                        dataNode.InnerText = data;
                    }
                }
                else if (colType == typeof(decimal)
                    || colType == typeof(float)
                    || colType == typeof(double)
                    )
                {
                    if (numberFormat != null)
                    {
                        decimal d;
                        if (decimal.TryParse(data, out d))
                            dataNode.InnerText = d.ToString("#,##0", numberFormat);
                        else
                            dataNode.InnerText = "0";
                    }
                    else
                    {
                        decimal d;
                        if (decimal.TryParse(data, out d))
                            dataNode.InnerText = d.ToString("#,##0").Replace(",", ".");
                        else
                            dataNode.InnerText = "0";
                    }
                }
                else
                {
                    XmlNode CloneNode;
                    CloneNode = dataNode.CloneNode(true);

                    if (data.Contains("\n"))
                    {
                        string[] s = data.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
                        int i = 0;
                        foreach (string _item in s)
                        {
                            if (i == 0)
                            {
                                dataNode.InnerText = _item;
                            }
                            else
                            {
                                XmlNode breakNode = xmlTemplateDoc.CreateNode(dataNode.NodeType, dataNode.Prefix, "br", dataNode.NamespaceURI);
                                dataNode.ParentNode.AppendChild(breakNode);

                                XmlNode y;
                                y = CloneNode.CloneNode(true);
                                y.InnerText = _item;
                                dataNode.ParentNode.AppendChild(y);
                            }
                            i++;
                        }
                    }
                    else
                        dataNode.InnerText = data;
                    //dataNode.InnerText = data.Replace("\n", "//w:br [w:r/w:type=\"text-wrapping\"\"']");
                    //<w:br w:type="text-wrapping"/>
                }

                //Callback, for custom fill field
                if (CallBack != null)
                    CallBack(dataNode, fieldName, data);

                ////--Xóa những thẻ đánh dấu là merge field đi, nhưng ko cần thiết, vì chỉ dùng làm mẫu để xuất
                //--Nếu để đoạn náy sẽ bị trường hợp nhiều thẻ merge trong 1 câu sẽ bị lỗi, vỡ

                //oDelColl = fieldNode.SelectNodes("w:r[w:fldChar]", dnsmgr);
                //if (oDelColl != null && oDelColl.Count > 0)
                //{
                //    int i = oDelColl.Count - 1;
                //    while (i >= 0)
                //    {
                //        oDelColl[i].ParentNode.RemoveChild(oDelColl[i]);
                //        i--;
                //    }
                //}
                //oDelNode = fieldNode.SelectSingleNode("w:r[w:instrText]", nsmgr);
                //if (oDelNode != null)
                //{
                //    oDelNode.ParentNode.RemoveChild(oDelNode);
                //}
            }
        }

        String templateRepeat;
        public void Transform(bool repeat =true)
        {
            templateRepeat = xmlTemplateDoc.SelectNodes("//w:body", nsmgr)[0].InnerXml ;
            //
            foreach (DataTable dt in dsData.Tables)
            {
                //
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    FormGlobals.App_ProgressText("Xử lý: " + (i+1).ToString() + " /" + dt.Rows.Count.ToString() + ": " + dt.Rows[i]["FullName"]);
                    //
                    DataTable dtTmp = dt.Clone();
                    //
                    if(i > 0)
                        xmlTemplateDoc.SelectSingleNode("//w:body", nsmgr).InnerXml = xmlTemplateDoc.SelectSingleNode("//w:body", nsmgr).InnerXml.Insert(
                            xmlTemplateDoc.SelectSingleNode("//w:body", nsmgr).InnerXml.Length,
                            "<w:p><w:r><w:br w:type=\"page\" /></w:r></w:p>" + 
                            templateRepeat
                        );
                    //
                    dtTmp.ImportRow(dt.Rows[i]);
                    //
                    TransformDataTable(dtTmp);
                }
                //
            }
            RemoveMailMergeNode();
            RemoveUnfilledFields();
        }

        //public void Transform()
        //{
        //    foreach (DataTable dt in dsData.Tables)
        //    {
        //        TransformDataTable(dt);
        //    }
        //    RemoveMailMergeNode();
        //    RemoveUnfilledFields();
        //}

        public void Transform(string imagePath)
        {
            if (imagePath.Trim().Length > 0)
                ReplaceImageData(xmlTemplateDoc.DocumentElement, imagePath);
            else
                RemoveUnfilledImage();

            foreach (DataTable dt in dsData.Tables)
            {
                TransformDataTable(dt);
            }
            RemoveMailMergeNode();
            RemoveUnfilledFields();
        }

        private void TransformDataTable(DataTable dt)
        {
            //First check tables in templates
            TransformWordMLTableRepeat(dt);
            //Next check other word ML elements
            TransformWordMLElementRepeat(dt);
            //Next check all other objects for non-repeating elements
            TransformNoRepeat(dt);

        }

        private void TransformWordMLTableRepeat(DataTable dt)
        {
            string tableName = dt.TableName;
            XmlNodeList oColl = xmlTemplateDoc.SelectNodes("//w:tbl[contains(descendant::aml:annotation/@w:name, '" + tableName + repeatAttribute
                + "') and (contains(descendant::w:instrText, ' MERGEFIELD \"" + tableName
                + "') or contains(descendant::w:instrText, ' MERGEFIELD  " + tableName
                + "') or contains(descendant::w:instrText, ' MERGEFIELD " + tableName + "'))]"
                , nsmgr);
            XmlNode templateRowNode;
            if (oColl != null && oColl.Count > 0)
            {
                foreach (XmlNode tableNode in oColl)
                {
                    templateRowNode = tableNode.SelectSingleNode("w:tr[contains(descendant::w:instrText, ' MERGEFIELD \"" + tableName
                        + "')or contains(descendant::w:instrText, ' MERGEFIELD  " + tableName
                        + "') or contains(descendant::w:instrText, ' MERGEFIELD " + tableName + "')]", nsmgr);
                    if (templateRowNode != null)
                    {
                        if (dt.Rows.Count > 0)
                        {
                            foreach (DataRow dr in dt.Rows)
                            {
                                TransformDataRow(dr, tableNode, templateRowNode);
                            }
                            tableNode.RemoveChild(templateRowNode);
                        }
                        else//Không có dữ liệu thì ẩn luôn table
                        {
                            XmlNode mailMergeNode;
                            mailMergeNode = xmlTemplateDoc.SelectSingleNode("//w:tbl[contains(descendant::aml:annotation/@w:name, '" + tableName + repeatAttribute
                            + "') and (contains(descendant::w:instrText, ' MERGEFIELD \"" + tableName
                            + "') or contains(descendant::w:instrText, ' MERGEFIELD  " + tableName
                            + "') or contains(descendant::w:instrText, ' MERGEFIELD " + tableName + "'))]", nsmgr);
                            if (mailMergeNode != null)
                            {
                                XmlNode parentNode = mailMergeNode.ParentNode;
                                parentNode.RemoveChild(mailMergeNode);
                            }
                        }
                    }
                }
            }

        }

        private void TransformWordMLElementRepeat(DataTable dt)
        {
            string tableName = dt.TableName;
            XmlNodeList oColl = xmlTemplateDoc.SelectNodes("//w:p[contains(descendant::aml:annotation/@w:name, '" + tableName + repeatAttribute
                + "') and (contains(descendant::w:instrText, ' MERGEFIELD \"" + tableName
                + "') or contains(descendant::w:instrText, ' MERGEFIELD  " + tableName
                + "') or contains(descendant::w:instrText, ' MERGEFIELD " + tableName + "'))]"
                , nsmgr);
            if (oColl != null && oColl.Count > 0)
            {
                foreach (XmlNode node in oColl)
                {
                    foreach (DataRow dr in dt.Rows)
                    {
                        TransformDataRow(dr, node);
                    }
                }
            }
        }


        private void TransformNoRepeat(DataTable dt)
        {
            if (dt.Rows.Count > 0)
            {
                DataRow firstRow = dt.Rows[0];
                string templateFieldName;
                for (int i = 0; i < firstRow.ItemArray.Length; i++)
                {
                    templateFieldName = dt.TableName + dt.Columns[i].ColumnName;
                    ReplaceFieldData(xmlTemplateDoc.DocumentElement, templateFieldName, firstRow[i].ToString(), dt.Columns[i].DataType);
                }
            }
        }

        private void TransformDataRow(DataRow dr, XmlNode tableNode, XmlNode templateRowNode)
        {
            DataTable dt = dr.Table;
            string tableName = dt.TableName;
            string templateFieldName;
            XmlNode cloneNode;
            cloneNode = templateRowNode.CloneNode(true);
            for (int i = 0; i < dr.ItemArray.Length; i++)
            {
                templateFieldName = dt.TableName + dt.Columns[i].ColumnName;
                ReplaceFieldData(cloneNode, templateFieldName, dr[i].ToString(), dt.Columns[i].DataType);
            }
            tableNode.InsertBefore(cloneNode, templateRowNode);
        }

        private void TransformDataRow(DataRow dr, XmlNode baseNode)
        {
            DataTable dt = dr.Table;
            string tableName = dt.TableName;
            string templateFieldName;
            XmlNode cloneNode, parentNode = baseNode.ParentNode;
            if (parentNode != null)
            {
                cloneNode = baseNode.CloneNode(true);
                for (int i = 0; i < dr.ItemArray.Length; i++)
                {
                    templateFieldName = dt.TableName + dt.Columns[i].ColumnName;
                    ReplaceFieldData(cloneNode, templateFieldName, dr[i].ToString(), dt.Columns[i].DataType);
                }
                parentNode.InsertBefore(cloneNode, baseNode);
                parentNode.RemoveChild(baseNode);
            }
        }

        public static string GetDataStructure(DataSet ds)
        {
            string sHeaderRow = string.Empty, sDummyRow = string.Empty;
            int i;
            foreach (DataTable dt in ds.Tables)
            {
                for (i = 0; i < dt.Columns.Count; i++)
                {
                    if (sHeaderRow == string.Empty)
                    {
                        sHeaderRow += dt.TableName + dt.Columns[i].ColumnName;
                        sDummyRow += " ";
                    }
                    else
                    {
                        sHeaderRow += "\t" + dt.TableName + dt.Columns[i].ColumnName;
                        sDummyRow = "\t" + " ";
                    }
                }
            }
            return sHeaderRow + Environment.NewLine + sDummyRow;
        }
    }
}
